﻿using System;
using System.IO;
using System.Linq;

using HtmlAgilityPack;

namespace mvcForum.AddOns {

	public static class HTMLSanitizer {

		public static String SanitizeHTML(this String html) {
			return html.SanitizeHTML(new String[] { });
		}

		public static String SanitizeHTML(this String html, String[] blacklist) {
			HtmlDocument doc = new HtmlDocument();
			// For some reason, HtmlDocument converts Html entities (&gt; etc), so we need to
			// stop that, let's double encode!
			doc.LoadHtml(html.Replace("&", "&amp;"));

			StringWriter sw = new StringWriter();
			ConvertTo(doc.DocumentNode, sw, blacklist);
			sw.Flush();
			return sw.ToString();
		}


		private static void ConvertContentTo(HtmlNode node, TextWriter outText, String[] blacklist) {
			foreach (HtmlNode subnode in node.ChildNodes) {
				ConvertTo(subnode, outText, blacklist);
			}
		}

		public static void ConvertTo(HtmlNode node, TextWriter outText, String[] blacklist) {
			String html;
			switch (node.NodeType) {
				case HtmlNodeType.Comment:
					// We're not going to output comments!
					break;

				case HtmlNodeType.Document:
					ConvertContentTo(node, outText, blacklist);
					break;

				case HtmlNodeType.Text:
					html = ((HtmlTextNode)node).Text;

					// Is it in fact a special closing node output as text?
					if (HtmlNode.IsOverlappedClosingElement(html)) {
						break;
					}

					// Do we actually have any output??
					if (!String.IsNullOrWhiteSpace(html)) {
						outText.Write(HtmlEntity.DeEntitize(html));
					}
					break;

				case HtmlNodeType.Element:
					// Is this tag in the blacklist? If not we'll output the start tag!
					if (!blacklist.Contains(node.Name.ToLower())) {
						outText.Write(String.Format("<{0}>", node.Name));
					}
					else {
						outText.Write(String.Format("&lt;{0} {1}&gt;", node.Name, String.Join(" ", node.Attributes.Select(a => String.Format("{0}=\"{1}\"", a.Name, a.Value)).ToArray())));
					}

					if (node.HasChildNodes) {
						ConvertContentTo(node, outText, blacklist);
					}

					// Is this tag in the black-list? If not we'll output the end tag!
					if (!blacklist.Contains(node.Name.ToLower())) {
						outText.Write(String.Format("</{0}>", node.Name));
					}
					else {
						outText.Write(String.Format("&lt;/{0}&gt;", node.Name));
					}
					break;
			}
		}
	}
}